import {useUserStore} from '@/stores/modules/user.store'
import type {RouteComponent, RouteRecordRaw} from 'vue-router'
import {isNavigationFailure, NavigationFailureType, type RouteLocationRaw} from 'vue-router'
import {homeRoute, staticRoutes} from '@/router/static'
import router from '@/router'
import type {MenuVO, RawRouteComponent} from '@/model/route'
import {useWebConfigStore} from '@/stores/modules/webConfig.store'
import {closeShade} from '@/utils/pageShadeUtil'
import {findInTreeArray, messageError, messageWarning, treeArrayIterator, treeArrayToArray} from '@/utils/commonUtil'
import {i18n} from '@/lang'
import {Session} from '@/utils/storageUtil'
import {cacheKeys} from '@/stores/constant/cacheKey'
import {toRaw} from 'vue'

const modules = import.meta.glob('@/**/*.vue')

/**
 * 静态路由名称列表，这些路由在更新路由过程中不受影响，保持不动
 */
let staticRouteNames: string[]

const getStaticRouteNames = () => {
	if (!staticRouteNames) {
		staticRouteNames = treeArrayToArray(staticRoutes).map(item => <string>item.name)
	}
	return staticRouteNames
}

/**
 * 将后台返回的菜单数据处理成路由数据
 * @param dynamicMenus 后台返回的用户动态菜单数据
 * @param routes 用于接收返回结果的路由数组
 * @param isChildren 是否是次级菜单
 * @return RouteRecordRaw[] 路由数据
 */
export const menusToRoutes: (dynamicMenus: MenuVO[], routes?: RouteRecordRaw[], isChildren?: boolean) => RouteRecordRaw[] = (dynamicMenus, routes = <RouteRecordRaw[]>[], isChildren = false) => {
	for (let j = 0; j < dynamicMenus.length; j++) {
		const menuVO = dynamicMenus[j]
		const route: RouteRecordRaw = {
			path: <string><unknown>undefined,
			// component和children任意一个属性有值即可
			component: <RawRouteComponent><unknown>undefined,
			children: <RouteRecordRaw[]><unknown>undefined
		}
		routes.push(route)
		// 复制属性值
		Object.assign(route, menuVO)
		// 处理path，是 children 时去掉 path 开头的 /
		if (isChildren && route.path.startsWith('/')) {
			route.path = route.path.slice(1)
		}
		// 处理component
		if (route.component) {
			if (typeof menuVO.component === 'string') {
				// vue文件在src目录下的路径，包含文件扩展名
				const vueFilePath = menuVO.component
				route.component = <Lazy<RouteComponent>>modules[`/src/${vueFilePath}`]
			} else {
				route.component = <RawRouteComponent>toRaw(menuVO.component)
			}
		}
		// 页面默认都缓存
		route.meta!.keepAlive = route.meta!.keepAlive !== false
		// 处理下级
		if (Array.isArray(menuVO.children) && menuVO.children.length > 0) {
			route.children = menusToRoutes(menuVO.children, undefined, true)
		}
	}
	return routes
}

/**
 * 路由（RouteRecordRaw）转菜单（MenuVO）
 * @param route
 */
export const routeToMenu: (route: RouteRecordRaw) => MenuVO = route => {
	return <MenuVO><unknown>Object.assign({}, route)
}

/**
 * 根据路由名称删除路由
 * @param routeName
 */
export const removeRouteByName = (routeName: string): boolean => {
	let removed = false
	// 找到上级路由
	const parentRoute = findInTreeArray(router.getRoutes(), route => {
		if (!route.children || route.children.length === 0) {
			return false
		}
		for (const child of route.children) {
			if (child.name === routeName) {
				return true
			}
		}
		return false
	})
	// 删除路由时会自动删除其下级路由，因此先判断路由存在再进行删除，避免控制台报警告
	if (router.hasRoute(routeName)) {
		router.removeRoute(routeName)
		removed = true
	}
	// 满足以下所有条件则删除上级路由
	// 1. 上级路由存在
	// 2. 上级路由没有components
	// 3. 上级路由没有redirect
	// 4. 上级路由没有子路由 或 上级路由有且仅有一个子路由且该子路由已被删除
	if (parentRoute && !parentRoute.components && !parentRoute.redirect && ((!parentRoute.children || parentRoute.children.length === 0) || parentRoute.children.length === 1 && parentRoute.children[0].name === routeName)) {
		const removedInner = removeRouteByName(<string>parentRoute.name)
		if (removedInner) {
			removed = removedInner
		}
	}
	return removed
}

/**
 * 重新构建路由，包括动态路由和静态路由
 */
export const generateRoutes = () => {
	// 删除原有路由
	for (const route of router.getRoutes()) {
		// 删除路由时会自动删除其下级路由，因此先判断路由存在再进行删除，避免控制台报警告
		if (router.hasRoute(route.name!)) {
			router.removeRoute(route.name!)
		}
	}
	// 添加静态路由
	for (const route of staticRoutes) {
		if (route.component || (route.components && Object.keys(route.components).length > 0) || route.redirect || (route.children && route.children.length > 0)) {
			router.addRoute(route)
		} else {
			console.warn('该路由无效，请检查配置是否正确：', route)
		}
	}
	// 添加动态路由
	const userRoutes = menusToRoutes(useUserStore().dynamicMenus)
	for (const userRoute of userRoutes) {
		if (userRoute.component || (userRoute.components && Object.keys(userRoute.components).length > 0) || userRoute.redirect || (userRoute.children && userRoute.children.length > 0)) {
			router.addRoute(<string>homeRoute.name, userRoute)
		} else {
			console.warn('该路由无效，请检查配置是否正确：', userRoute)
		}
	}
	Session.set(cacheKeys.ROUTER_INIT, true)
}

/**
 * 增量更新动态路由
 */
export const updateDynamicRoutes = () => {
	// 最新动态路由
	const userRoutes = menusToRoutes(useUserStore().dynamicMenus)
	// 删除路由
	for (const route of router.getRoutes()) {
		if (getStaticRouteNames().includes(<string>route.name)) {
			continue
		}
		if (!findInTreeArray(userRoutes, userRoute => userRoute.name === route.name)) {
			removeRouteByName(<string>route.name)
		}
	}
	// 新增路由
	treeArrayIterator(userRoutes, userRoute => {
		if (!router.hasRoute(userRoute.name!)) {
			// 获取上级路由名称
			const parentRoute = findInTreeArray(userRoutes, route => {
				if (!route.children || route.children.length === 0) {
					return false
				}
				for (const child of route.children) {
					if (child.name === userRoute.name) {
						return true
					}
				}
				return false
			})
			if (userRoute.component || (userRoute.components && Object.keys(userRoute.components).length > 0) || userRoute.redirect || (userRoute.children && userRoute.children.length > 0)) {
				router.addRoute(parentRoute ? <string>parentRoute.name : <string>homeRoute.name, userRoute)
			} else {
				console.warn('该路由无效，请检查配置是否正确：', userRoute)
			}
		}
	})
	Session.set(cacheKeys.ROUTER_INIT, true)
}

/**
 * 删除动态路由，只保留静态路由
 */
export const removeAllDynamicRoutes = () => {
	// 删除原有路由
	for (const route of router.getRoutes()) {
		if (getStaticRouteNames().includes(<string>route.name)) {
			continue
		}
		// 删除路由时会自动删除其下级路由，因此先判断路由存在再进行删除，避免控制台报警告
		if (router.hasRoute(route.name!)) {
			router.removeRoute(route.name!)
		}
	}
}

/**
 * 导航失败有错误消息的路由push
 * @param to — 导航位置，同 router.push
 */
export const routePush = async (to: RouteLocationRaw) => {
	try {
		const res = await router.push(to)
		// 在导航守卫中返回false，中断了本次导航
		if (isNavigationFailure(res, NavigationFailureType.aborted)) {
			messageError('导航失败，被导航守卫拦截')
		}
		// 在当前导航还没有完成之前又有了一个新的导航。比如，在等待导航守卫的过程中又调用了router.push
		else if (isNavigationFailure(res, NavigationFailureType.cancelled)) {
			messageWarning('正在导航，请稍后再试')
		}
		// 导航被阻止，因为已经在目标位置了
		else if (isNavigationFailure(res, NavigationFailureType.duplicated)) {
			messageWarning('您已经在：' + i18n.global.t(<string>res.to.meta.title))
		}
	} catch (error) {
		messageError((<Error>error).message)
	}
}

/**
 * 获取第一个菜单
 */
export const getFirstRoute = (routes: MenuVO[]): MenuVO | undefined => {
	const routerPaths: string[] = []
	const routers = router.getRoutes()
	routers.forEach((item) => {
		if (item.path) routerPaths.push(item.path)
	})
	let find: MenuVO | undefined
	for (const menuVo of routes) {
		if (routerPaths.indexOf(menuVo.path) !== -1) {
			return menuVo
		} else if (menuVo.children && menuVo.children.length > 0) {
			find = getFirstRoute(menuVo.children)
			if (find) return find
		}
	}
	return find
}

/**
 * 处理菜单点击
 *
 * 菜单类型（{@link MenuMeta.type}）
 * 1-菜单
 * 2-页面组件
 * 3-接口
 * 4-仅结构（用于页面呈现树状结构，方便管理上下级关系，不参与任何权限管理，此类型的记录只含有name即可）
 * 5-外部链接
 *
 * @param menu 菜单数据
 */
export const onClickMenu = (menu: MenuVO) => {
	switch (menu.meta.type) {
			// 内部菜单
		case 1:
			routePush({name: menu.name}).then(() => {
			})
			break
			// 外部链接
		case 5:
			window.open(menu.path, '_blank')
			break
		default:
			messageError('导航失败，菜单类型无法识别')
			break
	}
	const webConfigStore = useWebConfigStore()
	if (webConfigStore.layout.shrink) {
		closeShade(() => {
			webConfigStore.setLayout('menuCollapse', true)
		})
	}
}

/**
 * 获取菜单的paths
 */
export const getMenuPaths = (menus: RouteRecordRaw[]): string[] => {
	let menuPaths: string[] = []
	menus.forEach((item) => {
		menuPaths.push(item.path)
		if (item.children && item.children.length > 0) {
			menuPaths = menuPaths.concat(getMenuPaths(item.children))
		}
	})
	return menuPaths
}
